﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.AccessControl;
using System.Threading;
using System.Dynamic;
using FileSystemAudit.Model;

namespace FileSystemAudit
{
    /// <summary>
    /// use later System.Threading.Tasks.Task,
    /// </summary>
    public class FolderAnalyzer : IFoderAnalyzer
    {
        System.Threading.Tasks.Task task;
        private Thread threadOfAnalyze;
        #region Ctor
        public FolderAnalyzer()
        {
            task = new System.Threading.Tasks.Task(Analyze);
        }

        public FolderAnalyzer(System.IO.DirectoryInfo directory, int depth)
        {
            Directory = directory;
            this.Folder = new FolderInfos();
            if (this.Folder.Uid == Guid.Empty)
                this.Folder.Uid = Guid.NewGuid();

            this.CurrentDept = depth;
            Insert();
        }

        public FolderAnalyzer(FolderInfos folder, int depth = 0)
        {
            this.Folder = folder;
            Directory = new System.IO.DirectoryInfo(folder.Path);
            this.CurrentDept = depth;
            Update();
        }

        private void Init()
        {
            this.Folder.Name = Directory.Name;
            this.Folder.CreatedDate = Directory.CreationTime;
            this.Folder.Path = Directory.FullName;
            this.Folder.LastAcessDate = Directory.LastAccessTime;
            this.Folder.ModifiedDate = Directory.LastWriteTime;

            Console.WriteLine("Init Analyse " + this.Folder.Name + " ( " + this.Folder.Path + " )");
        }

        private void Insert()
        {
            try
            {
                Init();
                FileSystemDatabaseEntities dataCtx = new FileSystemDatabaseEntities();
                dataCtx.FolderInfos.AddObject(this.Folder);
                dataCtx.SaveChanges();
            }
            catch (Exception exc)
            {
                ManageException<FolderInfos>(e => { Console.WriteLine(" exception on Insert for " + e.Name + "Comment : " + e.Comment); }, this.Folder, exc);
            }
        }

        private void Update()
        {
            try
            {
                Init();
                FileSystemDatabaseEntities dataCtx = null;
                dataCtx = new FileSystemDatabaseEntities();
                dataCtx.SaveChanges();
            }
            catch (Exception exc)
            {
                ManageException<FolderInfos>(e => { Console.WriteLine(" exception on Update for " + e.Name); }, this.Folder, exc);
            }
        }

        #endregion

        public System.IO.DirectoryInfo Directory { get; private set; }
        public FolderInfos Folder { get; private set; }
        public int CurrentDept { get; private set; }
        public FolderAnalyzerMonitoredCollection FolderAnalyzerCollection { get; internal set; }

        public void Analyze()
        {
            FileInfo[] fis = Directory.GetFiles();
            Folder.NumberOfFiles = fis.Length;
            DirectoryInfo[] dirs = Directory.GetDirectories();
            this.Folder.NumberOfFolder = dirs.Length;

            AnalyzeFiles(fis);
            AnalyzeFolders(dirs);

            try
            {
                DirectorySecurity directorySecurity = Directory.GetAccessControl();
                this.Folder.Comment = "directorySecurity: " + directorySecurity.ToString();
            }
            catch (Exception)
            {
            }
        }

        public void BeginAnalyze(object objCallback)
        {
            IAsyncResult ar = this.Folder;
            this.Analyze();
            AsyncCallback callback = objCallback as AsyncCallback;
            if (callback != null)
            {
                callback.Invoke(ar);
            }
        }

        public delegate void NotifyAnalyzeFinished(FolderInfos source);
        public event NotifyAnalyzeFinished OnAnalyzeFinished;

        public void BeginASyncAnalyze()
        {
            IAsyncResult ar = this.Folder;
            threadOfAnalyze = new Thread(new ThreadStart(Analyze));
            threadOfAnalyze.Priority = ThreadPriority.BelowNormal;
            threadOfAnalyze.Start();
            while (threadOfAnalyze.IsAlive)
            {

            }
            if (OnAnalyzeFinished != null)
            {
                OnAnalyzeFinished.Invoke(this.Folder);
            }
        }

        private void AnalyzeFolders(DirectoryInfo[] dirs)
        {
            var depth = this.CurrentDept + 1;

            foreach (var dir in dirs)
            {
                FolderAnalyzer f = new FolderAnalyzer(dir, depth);
                f.Folder.Parent = this.Folder.Uid;
                bool isAdded = FolderAnalyzerCollection.TryAdd(f, 100);
                while (!isAdded)
                {
                    Console.WriteLine("wait after FolderAnalyzerCollection...");
                    Console.WriteLine("try to add " + f.Folder.Name);
                    isAdded = FolderAnalyzerCollection.TryAdd(f, 33);
                    if (!isAdded)
                        Thread.Sleep(55);
                }
            }
        }

        void AnalyzeFiles(FileInfo[] fis)
        {
            System.Collections.Concurrent.ConcurrentQueue<Thread> threadQueue = new System.Collections.Concurrent.ConcurrentQueue<Thread>();

            foreach (FileInfo file in fis)
            {
                var start = new ParameterizedThreadStart(AnalyzeFile);
                Thread thread = new Thread(start);
                threadQueue.Enqueue(thread);
                thread.Start(file);
            }

            do
            {
                Thread thread = null;
                if (threadQueue.TryPeek(out thread))
                {
                    if (!thread.IsAlive)
                        threadQueue.TryDequeue(out thread);
                }
            } while (threadQueue.Count > 0);

            while (fileInformations.Count > 0)
            {
                FileInfos fileInformation = null;
                if (fileInformations.TryTake(out fileInformation))
                {
                    try
                    {
                        FileSystemDatabaseEntities dataCtx = null;
                        dataCtx = new FileSystemDatabaseEntities();
                        dataCtx.FileInfos.AddObject(fileInformation);
                        dataCtx.SaveChanges();
                    }
                    catch (Exception)
                    {
                        if (fileInformations.Contains(fileInformation) == false)
                            fileInformations.Add(fileInformation);
                    }
                }
            }
        }

        System.Collections.Concurrent.ConcurrentBag<FileInfos> fileInformations = new System.Collections.Concurrent.ConcurrentBag<FileInfos>();

        void AnalyzeFile(object fileInfo)
        {
            FileInfos fileInformation = null;
            FileInfo file = fileInfo as FileInfo;
            FileSystemDatabaseEntities dataCtx = null;

            try
            {
                fileInformation = new FileInfos();
                fileInformation.Uid = Guid.NewGuid();
                fileInformation.Name = file.Name;
                fileInformation.Path = file.FullName;

                fileInformation.CreatedDate = file.CreationTime;
                fileInformation.Extension = file.Extension;
                fileInformation.ModifiedDate = file.LastWriteTime;
                fileInformation.SizeInKo = file.Length / 1024;
                fileInformation.SizeInOctets = file.Length;
                fileInformation.SizeInMo = file.Length / 1024 / 1024;

                fileInformation.Comment = "file.IsReadOnly:" + file.IsReadOnly + ";";
                FileSecurity fileSecurity = file.GetAccessControl();
                fileInformation.Comment += "fileSecurity: " + fileSecurity + ";";

                if (fileSecurity.AccessRightType != null)
                    fileInformation.Comment += "AccessRightType: " + fileSecurity.AccessRightType.Name + ";";
                if (fileSecurity.AccessRuleType != null)
                    fileInformation.Comment += "AccessRuleType: " + fileSecurity.AccessRuleType.Name + ";";
                if (fileSecurity.AuditRuleType != null)
                    fileInformation.Comment += "AuditRuleType: " + fileSecurity.AuditRuleType.Name + ";";


                fileInformation.Folder = this.Folder.Uid;
                dataCtx = new FileSystemDatabaseEntities();
                dataCtx.FileInfos.AddObject(fileInformation);
                dataCtx.SaveChanges();

                try
                {
                    fileInformation.FileType = AddFileExtension(file.Extension);
                    dataCtx.SaveChanges();
                }
                catch (System.Data.SqlClient.SqlException sqlExc)
                {
                    ManageAnyException<string>(e => { Console.WriteLine(" error AddFileExtension(file.Extension) " + file.Extension); }, file.Extension, sqlExc);
                }
            }
            catch (Exception exc)
            {
                ManageException<FileInfos>(e => { fileInformations.Add(fileInformation); }, fileInformation, exc);
            }
            finally
            {
                if (dataCtx != null)
                    dataCtx.Dispose();
            }
        }

        public void ManageAnyException<T>(Action<T> actionOnException, T source, Exception exc)
        {
            try
            {
                var initForegroundColor = Console.ForegroundColor;
                var initBackgroundColor = Console.BackgroundColor;
                Console.ForegroundColor = ConsoleColor.Red;
                Console.BackgroundColor = ConsoleColor.White;

                actionOnException.Invoke(source);

                if (source != null)
                    Console.WriteLine(" exception on " + source.ToString());
                else
                    Console.WriteLine(typeof(T).Name + " is null ");

                Console.WriteLine(" StackTrace: " + exc.StackTrace);

                Console.ForegroundColor = initForegroundColor;
                Console.BackgroundColor = initBackgroundColor;
            }
            catch (Exception)
            {
            }
        }

        public void ManageException<T>(Action<T> actionOnException, T source, Exception exc) where T : IInfo
        {
            try
            {
                var initForegroundColor = Console.ForegroundColor;
                var initBackgroundColor = Console.BackgroundColor;
                Console.ForegroundColor = ConsoleColor.Red;
                Console.BackgroundColor = ConsoleColor.White;

                actionOnException.Invoke(source);

                if (source != null)
                    Console.WriteLine(" exception on " + source.Name);
                else
                    Console.WriteLine(typeof(T).Name + " is null ");

                Console.WriteLine(" StackTrace: " + exc.StackTrace);

                Console.ForegroundColor = initForegroundColor;
                Console.BackgroundColor = initBackgroundColor;
            }
            catch (Exception)
            {
            }
        }


        public int AddFileExtension(string ext)
        {
            int extensionID = 0;
            try
            {
                FileSystemDatabaseEntities dataCtx = null;
                dataCtx = new FileSystemDatabaseEntities();
                var q = from e in dataCtx.FileTypeInfos where e.Extension.Equals(ext, StringComparison.InvariantCultureIgnoreCase) select e;
                if (q.Count() == 0)
                {
                    extensionID = dataCtx.FileTypeInfos.Count() + 1;

                    extensionID = (from e in dataCtx.FileTypeInfos select e).Count() + 1;

                    var queryCheckId = from e in dataCtx.FileTypeInfos where e.id == extensionID select e;

                    var n = queryCheckId.Count();
                    int nbTry = 0;
                    while (n != 0 || nbTry > 10)
                    {
                        extensionID = extensionID + 1;
                        n = queryCheckId.Count();
                        nbTry++;
                    }
                    extensionID = DateTime.Now.Millisecond;
                    if (queryCheckId.Count() != 0)
                    {
                        extensionID = Guid.NewGuid().GetHashCode();  // extensionID - DateTime.Now.Second;
                        Random rm = new Random(extensionID);
                        extensionID = rm.Next();
                    }
                    FileTypeInfos fileTypeInfos = new FileTypeInfos() { Extension = ext, id = extensionID };
                    dataCtx.FileTypeInfos.AddObject(fileTypeInfos);
                    dataCtx.SaveChanges();
                }
                else
                {
                    extensionID = q.FirstOrDefault().id;
                }
            }
            //[System.Data.SqlClient.SqlException]	{"Violation of PRIMARY KEY constraint 'PK_FileTypeInfos'.
            //Cannot insert duplicate key in object 'dbo.FileTypeInfos'. The duplicate key value is (537).\r\nThe statement has been terminated."}
            //System.Data.SqlClient.SqlException
            catch (System.Data.SqlClient.SqlException sql)
            {
                ManageAnyException<string>(e => { Console.WriteLine(ext + "extensionID: " + extensionID); }, ext, sql);
            }
            catch (Exception exc)
            {
                Console.WriteLine(exc.StackTrace);
                ManageAnyException<string>(e => { Console.WriteLine(ext + "extensionID: " + extensionID); }, ext, exc);
            }

            return extensionID;
        }


    }
}
